package com.ruoyi.app.controller.pay.service.impl;

import com.ruoyi.app.controller.order.mapper.IOrderRefundMapper;
import com.ruoyi.app.controller.pay.service.IPayService;
import com.ruoyi.app.controller.shop.service.IShopAppletService;
import com.ruoyi.common.constant.OrderConstants;
import com.ruoyi.common.entity.DO.shop.ShopAppletDO;
import com.ruoyi.common.entity.VO.PayVO;
import com.ruoyi.common.entity.VO.RefundVO;
import com.ruoyi.common.entity.wxpay.WXPay;
import com.ruoyi.common.entity.wxpay.WXPayConstants;
import com.ruoyi.common.entity.wxpay.WXPayUtil;
import com.ruoyi.common.entity.wxpay.WxConfig;
import com.ruoyi.common.exception.BusinessException;
import com.ruoyi.common.utils.reflect.ReflectUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

@Service
public class IPayServiceImpl implements IPayService {

    private static final Logger log = LoggerFactory.getLogger(IPayService.class);

    @Resource
    private IShopAppletService shopAppletService;

    @Resource
    private IOrderRefundMapper orderRefundMapper;

    @Value("${wx.pay.rechargeOrderNotifyUrl}")
    private String rechargeOrderNotifyUrl;

    @Value("${wx.pay.petrolOrderNotifyUrl}")
    private String petrolOrderNotifyUrl;

    @Value("${wx.pay.shopOrderBackRefundNotifyUrl}")
    private String shopOrderBackRefundNotifyUrl;

    @Value("${wx.pay.spbillCreateIp}")
    private String spbillCreateIp;

    @Value("${applet.appId}")
    private String appId;

    @Value("${applet.payKey}")
    private String payKey;

    @Value("${applet.mchId}")
    private String mchId;

    @Override
    @Transactional
    public Map<String, String> memberRechargeOrderPay(PayVO payVO) {
        Map map = new LinkedHashMap<>();
        WXPay wxpay;
        String totalFee = String.valueOf(payVO.getPayCost().multiply(new BigDecimal("100")).intValue());
        map.put("body", "会员充值-订单号" + payVO.getOrderSn());
        map.put("notify_url", rechargeOrderNotifyUrl);
        map.put("out_trade_no", payVO.getPaySn());
        map.put("openid", payVO.getOpenid());
        map.put("spbill_create_ip", spbillCreateIp);
        map.put("total_fee", totalFee);
        map.put("trade_type", WXPayConstants.TRADE_TYPE_JSAPI);
        try {
            wxpay = new WXPay(payVO.getWxConfig());
            Map<String, String> maps = wxpay.unifiedOrder(map);
            if (WXPayConstants.SUCCESS.equals(maps.get("return_code"))) {
                if(WXPayConstants.FAIL.equals(maps.get("result_code"))){
                    throw new BusinessException(maps.get("err_code_des"));
                }
                Map hashmap = new HashMap(15);
                String times = String.valueOf(WXPayUtil.getCurrentTimestamp());
                String str = WXPayUtil.generateNonceStr();
                String id = maps.get("prepay_id");
                hashmap.put("appId", payVO.getWxConfig().getAppID());
                hashmap.put("timeStamp", times);
                hashmap.put("nonceStr", str);
                hashmap.put("package", "prepay_id=" + id);
                hashmap.put("signType", WXPayConstants.HMACSHA256);
                String paySign = WXPayUtil.generateSignature(hashmap, payVO.getWxConfig().getKey(), WXPayConstants.SignType.HMACSHA256);
                hashmap.put("paySign", paySign);
                hashmap.put("out_trade_no", payVO.getPaySn());
                hashmap.put("total_fee", totalFee);
                return hashmap;
            } else {
                throw new BusinessException("统一下单失败！");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException("拉起支付失败！");
        }
    }

    @Override
    @Transactional
    public Map<String, String> petrolOrderPay(PayVO payVO) {
        Map map = new LinkedHashMap<>();
        WXPay wxpay;
        String totalFee = String.valueOf(payVO.getPayCost().multiply(new BigDecimal("100")).intValue());
        map.put("body", "一键加油-订单号" + payVO.getOrderSn());
        map.put("notify_url", petrolOrderNotifyUrl);
        map.put("out_trade_no", payVO.getPaySn());
        map.put("openid", payVO.getOpenid());
        map.put("spbill_create_ip", spbillCreateIp);
        map.put("total_fee", totalFee);
        map.put("trade_type", WXPayConstants.TRADE_TYPE_JSAPI);
        try {
            wxpay = new WXPay(payVO.getWxConfig());
            Map<String, String> maps = wxpay.unifiedOrder(map);
            if (WXPayConstants.SUCCESS.equals(maps.get("return_code"))) {
                Map hashmap = new HashMap(15);
                String times = String.valueOf(WXPayUtil.getCurrentTimestamp());
                String str = WXPayUtil.generateNonceStr();
                String id = maps.get("prepay_id");
                hashmap.put("appId", payVO.getWxConfig().getAppID());
                hashmap.put("timeStamp", times);
                hashmap.put("nonceStr", str);
                hashmap.put("package", "prepay_id=" + id);
                hashmap.put("signType", WXPayConstants.HMACSHA256);
                String paySign = WXPayUtil.generateSignature(hashmap, payVO.getWxConfig().getKey(), WXPayConstants.SignType.HMACSHA256);
                hashmap.put("paySign", paySign);
                hashmap.put("out_trade_no", payVO.getPaySn());
                hashmap.put("total_fee", totalFee);
                return hashmap;
            } else {
                throw new BusinessException("统一下单失败！");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException("拉起支付失败！");
        }
    }

    @Override
    public Map<String, String> orderRefund(RefundVO refundVO) {
        Map map = new LinkedHashMap<>();
        WXPay wxpay = null;
        String totalFee = String.valueOf(refundVO.getPayCost().multiply(new BigDecimal("100")).intValue());
        String refundFee = String.valueOf(refundVO.getRefundAmount().multiply(new BigDecimal("100")).intValue());
        map.put("out_trade_no", refundVO.getPaySn());
        map.put("out_refund_no", refundVO.getOrderSn());
        if (0 == refundVO.getOrderType()) {
            map.put("notify_url", shopOrderBackRefundNotifyUrl);
        } else if (1 == refundVO.getOrderType()) {
            map.put("notify_url", shopOrderBackRefundNotifyUrl);
        }
        map.put("total_fee", totalFee);
        map.put("refund_fee", refundFee);
        try {
            wxpay = new WXPay(refundVO.getWxConfig());
            Map resultMap = wxpay.refund(map);
            if (resultMap.get("return_msg").equals("OK") && resultMap.get("result_code").equals("SUCCESS")) {
                //退款中（这里其实已经可以确定退款成功了，但是最好还是等回调修改）
                orderRefundMapper.updateRefundStatusByRefundSn(refundVO.getOrderSn(), OrderConstants.RefundStatusEnum.REFUNDING.getValue());
                return map;
            } else {
                throw new BusinessException(resultMap.get("err_code_des").toString());
            }
        } catch (BusinessException e) {
            throw e;
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException("微信退款失败，请联系客服处理！");
        }
    }

    @Override
    public String wxPayNotifyHandle(HttpServletRequest request, String callBackClassName) {
        String errorXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
        String successXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
        try {
            Map<String, String> params = getWxPayNotifyParams(request);
//            {transaction_id=4200000806202102031022222986, nonce_str=XTczoioW4VTvovFHwZcq6ShLIKkqjWQP, bank_type=CCB_DEBIT, openid=oxL-s5bPMNbAJmST_fybTyhmJn40, sign=ACDB7C4BA3ADF8CD7757EA8BD743FD3F5FF1058FC4926770C388B0FD40421283, fee_type=CNY, mch_id=1604509863, cash_fee=1, out_trade_no=P2021020300270410449, appid=wx84d85cf767b38bab, total_fee=1, trade_type=JSAPI, result_code=SUCCESS, time_end=20210203002710, is_subscribe=N, return_code=SUCCESS}
            if (SpringUtils.containsBean(callBackClassName)) {
                Object callbackBean = SpringUtils.getBean(callBackClassName);
                Object[] notifyArr = new Object[1];
                Map<String, String> notifyMap = new HashMap<>();
                notifyMap.put("paySn", params.get("out_trade_no"));
                notifyMap.put("wxAmount", params.get("total_fee"));
//                notifyMap.put("payTerminal", params.get("device_info"));
                notifyMap.put("payWay", String.valueOf(OrderConstants.OrderPayWayEnum.WX_PAY.getValue()));
                notifyArr[0] = notifyMap;
                ReflectUtils.invokeMethodByName(callbackBean, "payNotifyHandle", notifyArr);
                return successXml;
            } else {
                return errorXml;
            }
        } catch (BusinessException e) {
            log.error(e.getMessage());
            return errorXml;
        } catch (RuntimeException e) {
            e.printStackTrace();
            log.error("回调数据已接收，但业务处理出现异常！");
            return errorXml;
        } catch (Exception e) {
            e.printStackTrace();
            return errorXml;
        }
    }

    @Override
    public String wxRefundNotifyHandle(HttpServletRequest request, String callBackClassName) {
        String errorXml = "<xml>" + "<return_code><![CDATA[FAIL]]></return_code>" + "<return_msg><![CDATA[报文为空]]></return_msg>" + "</xml> ";
        String successXml = "<xml>" + "<return_code><![CDATA[SUCCESS]]></return_code>" + "<return_msg><![CDATA[OK]]></return_msg>" + "</xml> ";
        try {
            Map<String, String> params = getWxPayNotifyParams(request);
            if (SpringUtils.containsBean(callBackClassName)) {
                Object callbackBean = SpringUtils.getBean(callBackClassName);
                Object[] notifyArr = new Object[1];
                Map<String, String> notifyMap = new HashMap<>();
                notifyMap.put("refundPaySn", params.get("out_trade_no"));
                notifyMap.put("refundSn", params.get("out_refund_no"));
                notifyMap.put("wxAmount", params.get("total_fee"));
                notifyArr[0] = notifyMap;
                ReflectUtils.invokeMethodByName(callbackBean, "refundNotifyHandle", notifyArr);
                return successXml;
            } else {
                return errorXml;
            }
        } catch (BusinessException e) {
            log.error(e.getMessage());
            return errorXml;
        } catch (RuntimeException e) {
            e.printStackTrace();
            log.error("回调数据已接收，但业务处理出现异常！");
            return errorXml;
        } catch (Exception e) {
            e.printStackTrace();
            return errorXml;
        }
    }


    private Map<String, String> getWxPayNotifyParams(HttpServletRequest request) {
        InputStream inputStream;
        try {
            inputStream = request.getInputStream();
        } catch (IOException e) {
            throw new BusinessException("读取回调数据流异常！");
        }
        //将InputStream转换成xmlString
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder sb = new StringBuilder();
        String line;
        try {
            while ((line = reader.readLine()) != null) {
                sb.append(line).append("\n");
            }
            Map<String, String> notifyMap = WXPayUtil.xmlToMap(sb.toString());
            ShopAppletDO shopApplet = shopAppletService.getDetailByAppId(notifyMap.get("appid"));
            WxConfig config = new WxConfig();
            if (shopApplet == null) {
                config.setMchID(mchId);
                config.setAppID(appId);
                config.setKey(payKey);
                config.setCertData(0L);
            } else {
                config.setMchID(shopApplet.getAppletMchId());
                config.setAppID(shopApplet.getAppletAppId());
                config.setKey(shopApplet.getAppletPayKey());
                config.setCertData(shopApplet.getShopId());
            }
            WXPay wxpay = new WXPay(config);
            if (wxpay.isPayResultNotifySignatureValid(notifyMap)) {
                if (!notifyMap.containsKey("return_code") || !notifyMap.get("return_code").equals("SUCCESS")) {
                    throw new BusinessException("手机支付回调通知签名错误！");
                }
                return notifyMap;
            } else {
                // 签名错误，如果数据里没有sign字段，也认为是签名错误
                throw new BusinessException("手机支付回调通知签名错误！");
            }
        } catch (IOException e) {
            throw new BusinessException("转换回调数据流异常！");
        } catch (Exception e) {
            e.printStackTrace();
            throw new BusinessException("微信配置异常！");
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}
